package com.runjian.rundo.iot.agent.business.control.executor.impl;

import java.util.Date;
import java.util.concurrent.TimeUnit;

import org.slf4j.MDC;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;

import com.runjian.rundo.bmp.kernel.common.spring.context.utils.MdcUtils;
import com.runjian.rundo.iot.agent.business.control.cache.DeviceControlCache;
import com.runjian.rundo.iot.agent.business.control.config.ControlConfigurationProperties;
import com.runjian.rundo.iot.agent.business.control.event.DeviceControlResponseEvent;
import com.runjian.rundo.iot.agent.business.control.executor.DeviceCommandExecutor;
import com.runjian.rundo.iot.agent.business.publisher.DeviceEventPublisher;
import com.runjian.rundo.iot.agent.common.codec.DeviceMessageCodec;
import com.runjian.rundo.iot.agent.common.context.DeviceMessageEncodeContext;
import com.runjian.rundo.iot.agent.common.entity.AgentDeviceDTO;
import com.runjian.rundo.iot.agent.common.entity.CommonDeviceCommand;
import com.runjian.rundo.iot.agent.common.manager.DeviceManager;
import com.runjian.rundo.iot.agent.common.session.Session;
import com.runjian.rundo.iot.agent.common.session.manager.SessionManager;
import com.runjian.rundo.iot.agent.common.thread.ScheduledExecutorFactory;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 控制指令执行器
 *
 * @author CaoQingHua
 */
@Slf4j
@RequiredArgsConstructor
public class DefaultDeviceCommandExecutor implements DeviceCommandExecutor {

    private final ControlConfigurationProperties configurationProperties;

    private final ScheduledExecutorFactory scheduledExecutorFactory;

    private final DeviceMessageCodec codec;

    private final SessionManager deviceSessionManager;

    private final DeviceControlCache deviceCache;

    private final DeviceManager deviceManager;

    @Override
    public void execute(CommonDeviceCommand deviceCommand) {
        String deviceId = null;
        String traceId = null;
        try {
            if (log.isDebugEnabled()) {
                log.info("[下发控制指令] 开始解析控制指令：{}", deviceCommand);
            }
            traceId = deviceCommand.getTraceId();
            deviceId = deviceCommand.getDeviceId();
            CommonDeviceCommand commonDeviceCommand = deviceCache.getCommonDeviceCommand(deviceId, traceId);
            if (null != commonDeviceCommand) {
                log.warn("[下发控制指令] 当前控制指令traceId与上一条控制指令traceId一样, 不进行控制指令下发, 设备Id:{}, traceId = {}", deviceId, traceId);
                DeviceEventPublisher.publishDeviceEvent(DeviceControlResponseEvent.failed(deviceId, deviceCommand, "重复的下发的操作"));
                return;
            }
            // 验证下发的时间与当前时间相隔的最大时间差
            Date currentTime = DateUtil.date();
            long betweenSecond = DateUtil.between(DateUtil.date(deviceCommand.getTime().toInstant()), currentTime, DateUnit.SECOND);
            if (betweenSecond > configurationProperties.getDateBetweenMaxSeconds()) {
                String errMsg = StrUtil.format("当前控制指令时间间隔超过最大时间间隔, 指令下发时间:{},当前时间:{}, 相差{}秒,允许最大时间差:{}",
                    DateUtil.date(deviceCommand.getTime().toInstant()), currentTime, betweenSecond,
                    configurationProperties.getDateBetweenMaxSeconds());
                log.warn("[下发控制指令] {}", errMsg);
                DeviceEventPublisher.publishDeviceEvent(DeviceControlResponseEvent.failed(deviceId, deviceCommand, errMsg));
                return;
            }
            // 缓存控制指令
            deviceCache.cacheCommonDeviceCommand(deviceCommand);
            // 超时检测机制
            responseWhenTimeout(deviceCommand);
            // 下发执行
            executeCommand(deviceId);
        } catch (Exception ep) {
            log.error("[下发控制指令失败] 设备下发异常,设备Id:{}, traceId：{},控制数据:{},异常内容： ", deviceCommand.getDeviceId(), deviceCommand.getTraceId(),
                deviceCommand, ep);
            DeviceEventPublisher.publishDeviceEvent(DeviceControlResponseEvent.failed(deviceId, deviceCommand, "下发控制失败"));
        }
    }

    /**
     * 添加超时检测任务
     *
     * @param deviceCommand 设备控制指令对象
     */
    protected void responseWhenTimeout(CommonDeviceCommand deviceCommand) {
        // 获取超时检测时间
        Long timeout = (null != deviceCommand.getTimeOut() && deviceCommand.getTimeOut() > 0) ? deviceCommand.getTimeOut()
            : configurationProperties.getTimeout();
        // 添加超时检测任务
        scheduledExecutorFactory.addSchedule(MdcUtils.wrap(() -> {
            CommonDeviceCommand command = this.deviceCache.getCommonDeviceCommand(deviceCommand.getDeviceId(), deviceCommand.getTraceId());
            if (command != null) {
                // 检查控制项换存还存在此数据, 表明肯定是超时了
                String errorMessage =
                    StrUtil.format("控制指令超时：设备Id:{}, traceId:{},控制数据:{}", deviceCommand.getDeviceId(), deviceCommand.getTraceId());
                log.warn(errorMessage);
                DeviceEventPublisher
                    .publishDeviceEvent(DeviceControlResponseEvent.failed(deviceCommand.getDeviceId(), deviceCommand, errorMessage));
            }
        },  MDC.getCopyOfContextMap()),timeout, TimeUnit.SECONDS);
    }

    /**
     * 执行下发,异步下发,此方式,可以加快效率
     *
     * @param deviceId 设备Id
     */
    @Async
    protected void executeCommand(String deviceId) {
        // 判断是否有控制指令正在下发中
        CommonDeviceCommand commonDeviceCommand = this.deviceCache.getCurrentDownCommon(deviceId);
        if (null != commonDeviceCommand && commonDeviceCommand.getRemainingKeys().size() > 0) {
            if (log.isDebugEnabled()) {
                log.debug("当前设备控制指令正在下发中,设备Id:{},正在下发的控制数据:{}", deviceId, commonDeviceCommand);
            }
            return;
        }
        // 从队列头获取
        commonDeviceCommand = this.deviceCache.peekCommonDeviceCommand(deviceId);
        if (null == commonDeviceCommand) {
            return;
        }
        MdcUtils.setTraceIdIfAbsent(commonDeviceCommand.getTraceId());
        try {
            // 获取网关
            // todo 这里对接多个地址有问题。
            AgentDeviceDTO agentDeviceDTO = deviceManager.getDeviceById(commonDeviceCommand.getDeviceId());
            Assert.notNull(agentDeviceDTO, "未找到当前设备信息,设备Id:{}", commonDeviceCommand.getDeviceId());
            Assert.notBlank(agentDeviceDTO.getGatewayId(), "未找到当前设备信息关联的网关信息,设备Id:{},设备数据", agentDeviceDTO);
            // 获取session
            Session deviceSession = deviceSessionManager.getGatewaySession(agentDeviceDTO.getGatewayId());
            Assert.notNull(deviceSession, "未找到当前设备连接会话,设备Id:{},网关Id:{}", commonDeviceCommand.getDeviceId(), agentDeviceDTO.getGatewayId());
            // deviceSession可能为空，由具体的agent编解码器处理
            DeviceMessageEncodeContext encodeContext = new DeviceMessageEncodeContext(commonDeviceCommand, deviceSession);
            if (log.isDebugEnabled()) {
                log.debug("[下发控制指令] 开始进行指令下发, 控制数据:{}", commonDeviceCommand);
            }
            codec.encode(encodeContext);
        } catch (IllegalArgumentException ie) {
            String errMsg = StrUtil.format("下发控制失败,失败原因:{}", ie.getMessage());
            DeviceEventPublisher.publishDeviceEvent(DeviceControlResponseEvent.failed(deviceId, commonDeviceCommand, errMsg));
            log.error("[下发控制指令失败] 设备下发异常,设备Id:{}, traceId：{},控制数据:{},异常内容：{} ", commonDeviceCommand.getDeviceId(),
                commonDeviceCommand.getTraceId(), commonDeviceCommand, ie.getMessage());
        } catch (Exception e) {
            String errMsg = StrUtil.format("下发控制失败");
            DeviceEventPublisher.publishDeviceEvent(DeviceControlResponseEvent.failed(deviceId, commonDeviceCommand, errMsg));
            log.error("[下发控制指令失败] 设备下发异常,设备Id:{}, traceId：{},控制数据:{},异常内容： ", commonDeviceCommand.getDeviceId(),
                commonDeviceCommand.getTraceId(), commonDeviceCommand, e);
        }
    }

    /**
     * 控制结果通知事件, 此方式，可以愉
     *
     * @param deviceControlResponseEvent
     */
    @Override
    @EventListener(DeviceControlResponseEvent.class)
    public void notifyDeviceResponse(DeviceControlResponseEvent deviceControlResponseEvent) {
        CommonDeviceCommand commonDeviceCommand = deviceControlResponseEvent.getDeviceCommand();
        // 获取控制数据
        if (StrUtil.isBlank(deviceControlResponseEvent.getSeries()) || commonDeviceCommand.getRemainingKeys().size() <= 0) {
            if (log.isDebugEnabled()) {
                log.debug("控制事件处理,当前控制对象已全总反馈完成,现在处理下一个控制，当前事件触发控制对象:{}");
            }
            this.deviceCache.removeDeviceCommand(commonDeviceCommand.getDeviceId(), commonDeviceCommand.getTraceId());
            // 触发继续执行
            this.executeCommand(commonDeviceCommand.getDeviceId());
        } else {
            if (log.isDebugEnabled()) {
                log.debug("控制事件处理,当前控制制指令存在还未反馈的指标顶，不继续下发, 控制对象:{},未反馈的指标项:{}", commonDeviceCommand,
                    commonDeviceCommand.getRemainingKeys());
            }
        }
    }
}
